﻿using System;
using System.Collections;
using System.Globalization;
using System.Linq;

namespace SG.Ling.AutoTranslator.NLP
{
    public static class Detector
    {
        private static readonly BitArray[] RangeChecker;

        static Detector()
        {
            RangeChecker = new BitArray[0x10ffff.NumberOfBits()];
        }
        
        struct Counts
        {
            public readonly int Length;
            private readonly byte[] _categoryCounts;
            private readonly byte[] _languageCounts;

            public Counts(int textLength) : this()
            {
                Length = Math.Min(255, textLength);
                _categoryCounts = new byte[Support.NumberOfCategories];
                this[Category.Unspecified] = 1;
                _languageCounts = new byte[Support.NumberOfLanguages];
                this[Language.Unknown] = 1;
            }

            public void Increase(Category category)
            {
                _categoryCounts[(int) category]++;
            }
            public void Increase(Language language)
            {
                _languageCounts[(int)language]++;
            }

            public int this[Language language]
            {
                get { return _languageCounts[(int)language]; }
                set
                {
                    if (Byte.MinValue > value && value > Byte.MaxValue)
                        throw new ArgumentOutOfRangeException("value", value, string.Format("Should: {0} <= x <= {1}", Byte.MinValue, Byte.MaxValue));
                    _languageCounts[(int)language] = (byte) value;
                }
            }
            public int this[Category category]
            {
                get { return _categoryCounts[(int)category]; }
                set
                {
                    if (Byte.MinValue > value && value > Byte.MaxValue)
                        throw new ArgumentOutOfRangeException("value", value, string.Format("Should: {0} <= x <= {1}", Byte.MinValue, Byte.MaxValue));
                    _categoryCounts[(int)category] = (byte)value;
                }
            }
        }

        public static CultureInfo DetectLanguage(this string text)
        {
            if (text == null)
                throw new ArgumentNullException("text");

            var counts = new Counts(text.Length);

            for (var i = 0; i < counts.Length; i++)
            {
                var @char = text[i];
                switch (@char)
                {
                    case ' ':
                        counts.Increase(Category.Space);
                        break;

                    case 'ĳ':
                    case 'Ĳ':
                        counts.Increase(Language.Dutch);
                        break;

                    case 'İ':
                    case 'ı':
                        counts.Increase(Language.Turkish);
                        break;

                    case 'œ':
                        counts.Increase(Language.French);
                        break;

                    case 'ç':
                        counts.Increase(Language.French);
                        counts.Increase(Language.Turkish);
                        break;

                    case '¿':
                    case '¡':
                        counts.Increase(Language.Spanish);
                        break;

                    case 'é':
                    case 'è':
                    case 'à':
                    case 'á':
                    case 'ó':
                    case 'ò':
                    case 'í':
                    case 'ì':
                        counts.Increase(Category.Pinyin);
                        counts.Increase(Category.Latinate);
                        break;

                    case 'É':
                    case 'È':
                    case 'À':
                    case 'Á':
                    case 'Ò':
                    case 'Ó':
                    case 'Ì':
                    case 'Í':
                    case 'Ô':
                    case 'Û':
                    case 'ô':
                    case 'û':
                        counts.Increase(Category.Latinate);
                        break;

                    case 'ä':
                    case 'ö':
                    case 'ü':
                    case 'Ä':
                    case 'Ö':
                    case 'Ü':
                        counts.Increase(Category.Umlauts);
                        break;

                    case 'ß':
                        counts.Increase(Language.German);
                        break;

                    default:
                        if (@char >= 0x23 && @char <= 0x3F)
                            counts.Increase(Language.NonLanguageContent);
                        if (@char >= 'A' && @char <= 'Z')
                            counts.Increase(Category.Abc);
                        if (@char >= 'a' && @char <= 'z')
                            counts.Increase(Category.Abc);
                        if (@char >= 0x3040 && @char <= 0x30FF)
                            counts.Increase(Language.Japanese);
                        if (@char >= 0x4E00 && @char <= 0x9FFF)
                            counts.Increase(Language.Chinese);
                        if (@char >= 0xAC00 && @char <= 0xD7AF)
                            counts.Increase(Language.Korean);
                        break;
                }
            }

            var orderedByCounts =
                (from index in Enumerable.Range(0, Support.NumberOfLanguages)
                 let language = (Language) index
                 where language != Language.NonLanguageContent
                 let count = counts[language]
                 orderby count descending
                 select new {Language = language, Count = count}).ToArray();

            var detected = orderedByCounts.First();
            if (detected.Language == Language.Unknown)
            {
                if (orderedByCounts[1].Count == detected.Count)
                    detected = orderedByCounts[1];
            }
            return detected.Language.ToCulture();
        }
    }
}
